<?php
/**
 * 功能： MContext 封装了运行时上下文
 * 说明： 获取访问控件器
 * 创建日期：2012-04-12
 * 更新日期：
 * 作者：Mirze
 * 补充说明： 
 * 
 */
class MContext implements ArrayAccess
{
	/** 指示 UDI 中的部分 **/   
    const UDI_ALL        = 'all';   // UDI 中的所有部分    
    const UDI_CONTROLLER = 'controller';  // UDI 中的控制器    
    const UDI_ACTION     = 'action';  // UDI 中的动作    
    const UDI_MODULE     = 'module';  // UDI 中的模块	
    const UDI_NAMESPACE  = 'namespace';  // UDI 中的名字空间

    /** 指示 UDI 的默认值 **/    
    const UDI_DEFAULT_CONTROLLER = 'index';  // 默认控制器    
    const UDI_DEFAULT_ACTION     = 'index';  // 默认动作    
    const UDI_DEFAULT_MODULE     = 'default';  // 默认的模块	
    const UDI_DEFAULT_NAMESPACE  = 'default'; // 默认的名字空间

	/** 请求包含的控制器名称 **/
    public $controller_name;
    /** 请求包含的动作名 **/
    public $action_name;
	public $namespace;
	public $module_name;  

   /** 附加的参数 **/
    private $_params = array();

    /** 路由对象 **/
    private $_router;
    /** 当前请求 URL **/
    static private $_request_uri;
    /** 当前请求 URL 不包含查询参数的部分 **/
    static private $_base_uri;
    /** 当前请求 URL 的目录部分 **/
    static private $_base_dir;

    /** 路由模式 **/
    static private $_url_mode;

	/** UDI 的默认值 **/
    private static $_udi_defaults = array(
        self::UDI_MODULE => self::UDI_DEFAULT_MODULE,
        self::UDI_CONTROLLER => self::UDI_DEFAULT_CONTROLLER,
        self::UDI_ACTION     => self::UDI_DEFAULT_ACTION,
		self::UDI_NAMESPACE => self::UDI_DEFAULT_NAMESPACE,
    );

	/**  **/
	function __construct()
	{
		$this->reinit();
	}

	/**
	 * (功能描述)  返回 MContext 对象的唯一实例
     *
     * @code php
     * $context = MContext::instance();
     * @endcode
     *
     * @return MContext MContext 对象的唯一实例
     */
    static function instance()
    {
        static $instance;
        if (is_null($instance)) $instance = new MContext();
        return $instance;
    }

	/**
	 * (功能描述) 根据服务器运行环境，重新初始化 QContext 对象
     *
     * @param boolean $full_init 是否进行完全的初始化
     */
    function reinit($full_init = false)
    {
        if ($full_init) {
            $this->_params = array();
            $this->_router = null;
        }

        self::$_request_uri = null;
        self::$_base_uri    = null;
        self::$_base_dir    = null;
        self::$_url_mode    = null;

        // 从 $_GET 中提取请求参数
        $keys = array_keys($_GET);
        if (!empty($keys)) {
            $keys = array_combine($keys, $keys);
            $keys = array_change_key_case($keys);
        }

		/** 拼接访问控件器 **/
        $udi = array();
        $udi[self::UDI_CONTROLLER] = (isset($keys[self::UDI_CONTROLLER])) ? $_GET[$keys[self::UDI_CONTROLLER]] : null;
        $udi[self::UDI_ACTION]     = (isset($keys[self::UDI_ACTION])) ? $_GET[$keys[self::UDI_ACTION]] : null;
        $udi[self::UDI_MODULE]     = (isset($keys[self::UDI_MODULE])) ? $_GET[$keys[self::UDI_MODULE]] : null;
		$udi[self::UDI_NAMESPACE]  = (isset($keys[self::UDI_NAMESPACE])) ? $_GET[$keys[self::UDI_NAMESPACE]] : null;

        $this->changeRequestUDI($udi);
    }

	/** 格式化UDI： 返回 UDI 的字符串表现形式 **/
	function normalizeUDI($udi, $return_array = true)
    {
		if (!is_array($udi)) {
            // 特殊处理 ".", "/" UDI解析
            // url('.') 返回当前动作
            // url('/') 返回当前控制器+index
            if($udi === '.') {
                $namespace = $this->namespace;
                $module_name = $this->module_name;
                $controller = $this->controller;
                $action = $this->action;
            } elseif($udi === '/') {
                $namespace = $this->namespace;
                $module_name = $this->module_name;
                $controller = $this->controller;
                $action = self::$_udi_defaults[self::UDI_ACTION];
            } else {
                if (strpos($udi, '::') !== false) {
                    $arr = explode('::', $udi);
                    $namespace = array_shift($arr);
                    $udi = array_shift($arr);
                } else {
                    $namespace = $this->namespace;
                }

                if (strpos($udi, '@') !== false) {
                    $arr = explode('@', $udi);
                    $module_name = array_pop($arr);
                    $udi = array_pop($arr);
                } else {
                    $module_name = $this->module_name;
                }

                $arr = explode('/', $udi);
                $controller = array_shift($arr);
                $action = array_shift($arr);
            }

            $udi = array(                
                self::UDI_CONTROLLER => $controller,
                self::UDI_ACTION     => $action,
				self::UDI_MODULE     => $module_name,
                self::UDI_NAMESPACE  => $namespace,
            );
        }

        if (empty($udi[self::UDI_MODULE])) {
            $udi[self::UDI_MODULE] = $this->module_name;
        }
        if (empty($udi[self::UDI_NAMESPACE])) {
            $udi[self::UDI_NAMESPACE] = $this->namespace;
        }

        if (empty($udi[self::UDI_CONTROLLER])) {
            $udi[self::UDI_CONTROLLER] = $this->controller_name;
        }
        if (empty($udi[self::UDI_ACTION])) {
            $udi[self::UDI_ACTION] = self::UDI_DEFAULT_ACTION;
        }

        foreach (self::$_udi_defaults as $key => $value) {
            if (empty($udi[$key])) {
                $udi[$key] = $value;
            } else {
                $udi[$key] = preg_replace('/[^a-z0-9]+/', '', strtolower($udi[$key]));
            }
        }

        if (!$return_array) {
            return "{$udi[self::UDI_NAMESPACE]}::{$udi[self::UDI_CONTROLLER]}/{$udi[self::UDI_ACTION]}@{$udi[self::UDI_MODULE]}";
        } else {
            return $udi;
        }
	}

	/**
	 * (功能描述) 返回当前请求对应的 UDI : 将当前请求中包含的控制器、动作、名字空间和模块名提取出来，构造为一个 UDI。
     *
     * @code php
     * dump($context->requestUDI());
     * @endcode
     *
     * @param boolean $return_array 是否返回数组形式的 UDI
     * @return string|array 对应当前请求的 UDI
     */
    function requestUDI($return_array = true)
    {
        return $this->normalizeUDI("/{$this->action_name}", $return_array);
    }

	/**
	 * (功能描述) 将 MContext 对象保存的请求参数设置为 UDI 指定的值
     *
     * @code php
     * $context->changeRequestUDI('posts/edit');
     * // 将输出 posts
     * echo $context->controller_name;
     * @endcode
     *
     * @param array|string $udi 要设置的 UDI
     *
     * @return MContext 返回 MContext 对象本身，实现连贯接口
     */
    function changeRequestUDI($udi)
    {
        $udi = $this->normalizeUDI($udi);

        $this->controller_name = $udi[self::UDI_CONTROLLER];
        $this->action_name     = $udi[self::UDI_ACTION];
        $this->module_name     = $udi[self::UDI_MODULE];

        if ($this->module_name == self::UDI_DEFAULT_MODULE) {
            $this->module_name = null;
        }

        return $this;
    }


	/**
     * 确定是否包含指定的参数，实现 ArrayAccess 接口
     *
     * @code php
     * echo isset($context['title']);
     * @endcode
     *
     * @param string $parameter 要检查的参数
     *
     * @return boolean 参数是否存在
     */
    function offsetExists($parameter)
    {
        if (isset($_GET[$parameter]))
            return true;
        elseif (isset($_POST[$parameter]))
            return true;
        else
            return isset($this->_params[$parameter]);
    }

    /**
     * 设置附加参数，实现 ArrayAccess 接口
     *
     * 该方法功能同 __set() 魔法方法。
     *
     * @code php
     * $context['title'] = $title;
     * echo $context->param('title');
     * @endcode
     *
     * @param string $parameter 要设置的参数
     * @param mixed $value 参数值
     */
    function offsetSet($parameter, $value)
    {
        $this->changeParam($parameter, $value);
    }

    /**
     * 访问请求参数，实现 ArrayAccess 接口
     *
     * @code php
     * $title = $context['title'];
     * @endcode
     *
     * @param string $parameter 要访问的请求参数
     *
     * @return mixed 参数值
     */
    function offsetGet($parameter)
    {
        return $this->query($parameter);
    }

    /**
     * 取消附加参数，实现 ArrayAccess 接口
     *
     * 同 __unset() 方法，QContext::offsetUnset() 只影响 QContext 对象的附加参数。
     *
     * @code php
     * unset($context['title']);
     * @endcode
     *
     * @param string $parameter 要取消的附加参数
     */
    function offsetUnset($parameter)
    {
        unset($this->_params[$parameter]);
    }
}